extern crate argparse;

use std::env;
use std::fs;
use std::path::Path;
use dotenv;
use argparse::{ArgumentParser, StoreTrue, Store};
use duct::cmd;

struct Configuration {
    port: i16,
    user: String,
    webserver: String,
    url: String,
    identity: String,
    secret: String
}

struct Options {
    debug: bool,
    save: bool,
    command: String,
    file: String
}

fn remove_whitespace(s: &str) -> String {
    s.chars().filter(|c| !c.is_whitespace()).collect()
}

// TODO: needs a lot more error checking and beautiful output

fn pull(config: Configuration, options: Options) {
    let stdout = cmd!("curl", "-s", format!("{}{}", config.url, options.file))
        .pipe(cmd!("base64", "-d"))
        .pipe(cmd!("openssl", "enc", "-d", "-aes-256-cbc", "-k", config.secret))
        .read().unwrap();
    
    if options.save {
        fs::writeln(&options.file, stdout)
            .expect("Unable to write file");
    } else {
        println!("{}", stdout);
    }
}

fn push(config: Configuration, options: Options) {
    let file_name = Path::new(& options.file).file_name().unwrap().to_os_string().into_string().unwrap();
    let tmp_file_name = format!("/tmp/strlst-{}", file_name);

    if options.debug {
        println!("saving temporary file in {}", tmp_file_name);
    }

    let stdout1 = cmd!("cat", options.file)
        .pipe(cmd!("openssl", "enc", "-aes-256-cbc", "-k", config.secret))
        .pipe(cmd!("base64"))
        .read().unwrap();

    if options.debug {
        println!("{}", stdout1);
    }

    fs::write(&tmp_file_name, stdout1)
        .expect("Unable to write file");

    let stdout2 = cmd!("scp", "-P", config.port.to_string(), "-i", config.identity, tmp_file_name, format!("{}@{}:~/dropzone/\"{}\"", config.user, config.webserver, file_name))
        .read().unwrap();
    
    println!("{}", stdout2);
}

fn list(config: Configuration) {
    // launch subprocesses
    let stdout = cmd!("curl", "-s", config.url)
        .pipe(cmd!("grep", "href"))
        .pipe(cmd!("sed", "-e", "s/<[^<]*>//g"))
        .read().unwrap();
    println!("{}", stdout);
}

fn main() {
    // create configuration struct
    let mut config = Configuration {
        port: 222,
        user: "".to_string(),
        webserver: "".to_string(),
        url: "".to_string(),
        identity: "".to_string(),
        secret: "".to_string()
    };

    // TODO: error checking?
    let env_home = dotenv::var("HOME").unwrap();
    let env_user = dotenv::var("USER").unwrap();

    // TODO: check existence of files beforehand
    let stored_webserver = fs::read_to_string(format!("{}/.local/share/{}/.webserver", env_home, env_user))
        .expect("Something went wrong reading the webserver file");
    let stored_secret = fs::read_to_string(format!("{}/.local/share/{}/.secret", env_home, env_user))
        .expect("Something went wrong reading the secret file");

    // TODO: check whether contents of stored_(webserver|secret) are set

    // set up externally stored configuration options
    config.webserver = remove_whitespace(&stored_webserver);
    config.secret = remove_whitespace(&stored_secret);
    config.user = format!("{}", env_user);
    config.identity = format!("{}/.ssh/copyservice_rsa", env_home);
    config.url = format!("https://{}/unindexed/", config.webserver);

    // create options struct
    let mut options = Options {
        debug: false,
        save: false,
        command: "".to_string(),
        file: "".to_string()
    };

    // limit scope of ap
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("reimplements strlst shell script");
        ap.refer(&mut options.debug)
            .add_option(&["-d", "--debug"], StoreTrue, "enable verbosity");
        ap.refer(&mut options.save)
            .add_option(&["-s", "--save"], StoreTrue, "save output of pull, only useful in conjunction with pull command");
        ap.refer(&mut options.command)
            .add_option(&["-c", "--command"], Store, "command to execute, (pull|push|list)")
            .required();
        ap.refer(&mut options.file)
            .add_option(&["-f", "--file"], Store, "file to operate on");
        ap.parse_args_or_exit();
    }

    // check for additional constraints
    if options.save && (options.command == "push" || options.command == "list") {
        println!("{}: Save in conjunction with anything other than '--command pull' makes no sense", env::args().next().unwrap());
        return;
    } else if !options.save && options.file.is_empty() {
        println!("{}: Must specify file", env::args().next().unwrap());
        return;
    }

    // debug output
    if options.debug {
        println!("options: save={s} command={c} file={f}", s=options.save, c=options.command, f=options.file);
        println!("port: {}\nuser: {}\nwebserver: {}\nurl: {}\nidentity: {}\nsecret: {}", config.port, config.user, config.webserver, config.url, config.identity, config.secret);
    }

    match options.command.as_str() {
        "pull" => { pull(config, options); },
        "push" => { push(config, options); },
        "list" => { list(config); },
        _ => {
            println!("{}: valid commands are (pull|push|list)", env::args().next().unwrap());
        }
    }
}
